#ifdef _FNT_COMPUTE_THREAD_OPTIONS_H_

ComputeGeneralOptions* ComputeGeneralOptions::_instance{nullptr};
OptionsPage* OptionsPage::computeGeneralOptions() {
	return ComputeGeneralOptions::instance();
}

template<>
ComputeAlgOptions<ALG_FIND>* ComputeAlgOptions<ALG_FIND>::_instance{nullptr};
template<>
ComputeAlgOptions<ALG_CONN>* ComputeAlgOptions<ALG_CONN>::_instance{nullptr};
template<>
ComputeAlgOptions<ALG_REFINE>* ComputeAlgOptions<ALG_REFINE>::_instance{nullptr};
template<>
ComputeAlgOptions<ALG_REFINE_AUTO>* ComputeAlgOptions<ALG_REFINE_AUTO>::_instance{nullptr};

OptionsPage* OptionsPage::computeFindOptions() {
	return ComputeAlgOptions<ALG_FIND>::instance();
}
OptionsPage* OptionsPage::computeConnOptions() {
	return ComputeAlgOptions<ALG_CONN>::instance();
}
OptionsPage* OptionsPage::computeRefineOptions() {
	return ComputeAlgOptions<ALG_REFINE>::instance();
}
OptionsPage* OptionsPage::computeRefineAutoOptions() {
	return ComputeAlgOptions<ALG_REFINE_AUTO>::instance();
}


void ComputeGeneralOptions::spinboxChanged(int v) {
	intervalSet=v;
	notifyChange();
}
ComputeGeneralOptions::~ComputeGeneralOptions() {
}
ComputeGeneralOptions::ComputeGeneralOptions():
	OptionsPage{"General", "General Settings"},
	ctp{nullptr}, disabled{true}
{
	auto options=Tracer::instance()->options();
	if(!options->getInt("compute.update.interval", &intervalDef))
		intervalDef=200;

	auto flayout=new QFormLayout{};
	layout()->addLayout(flayout, 0);
	layout()->addStretch(1);

	spinbox=new QSpinBox{this};
	flayout->addRow("Update interval", spinbox);
	spinbox->setMaximum(2000);
	spinbox->setMinimum(0);
	spinbox->setValue(intervalDef);
	intervalSet=intervalDef;
	spinbox->setSingleStep(50);
	spinbox->setSuffix("ms");
	connect(spinbox, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &ComputeGeneralOptions::spinboxChanged);
	//

	/*
	label=new QLabel{"Set the interval in milli-seconds at which progress of running computation is visualized in the 3D viewer.\nUse 0 to supress visualization.\nNote that some algorithms might show nothing to indicate progress.", this};
	*/
	//label->setWordWrap(true);
}

void ComputeGeneralOptions::config(ComputeThreadPriv* _ctp, Options* options) const {
	if(!options->getInt("compute.update.interval", &_ctp->update_interval))
		_ctp->update_interval=intervalDef;
}
void ComputeGeneralOptions::saveConfig(ComputeThreadPriv* _ctp, Options* options) const {
	if(_ctp->update_interval==intervalDef) {
		options->removeKey("compute.update.interval");
	} else {
		options->setInt("compute.update.interval", _ctp->update_interval);
	}
}

void ComputeGeneralOptions::attach(ComputeThreadPriv* _ctp) {
	ctp=_ctp;
	auto blocked=setBlocked(true);
	if(ctp) {
		spinbox->setValue(ctp->update_interval);
		//auto enabled_save=spinbox->setBlocked(false);
	} else {
		spinbox->setValue(intervalDef);
	}
	setBlocked(blocked);
	notifyChange();
}
void ComputeGeneralOptions::getState(bool* a, bool* b, bool* c, bool* d) const {
	if(ctp) {
		bool notApplied=intervalSet!=ctp->update_interval;
		*a=notApplied&&!disabled;
		*b=notApplied;
	} else {
		*a=false;
		*b=false;
	}
	bool notDefault=intervalSet!=intervalDef;
	*c=notDefault;
	*d=notDefault;
}
void ComputeGeneralOptions::setStateImp(SessionState ts) {
	switch(ts) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
			disabled=true;
			break;
		case SessionState::Ready:
		case SessionState::Computing:
		case SessionState::LoadingCubes:
			disabled=false;
			break;
	}
}
void ComputeGeneralOptions::useDefaultImp() {
	spinbox->setValue(intervalDef);
}
void ComputeGeneralOptions::saveDefaultImp() {
	intervalDef=intervalSet;
	auto options=Tracer::instance()->options();
	if(intervalDef==200) {
		options->removeKey("compute.update.interval");
	} else {
		options->setInt("compute.update.interval", intervalDef);
	}
}
void ComputeGeneralOptions::applyChangesImp() {
	if(ctp)
		ctp->update_interval=intervalSet;
}
void ComputeGeneralOptions::resetChangesImp() {
	if(ctp)
		spinbox->setValue(ctp->update_interval);
}

template<AlgType typ> struct alg_helper;
template<> struct alg_helper<ALG_FIND> {
	using alg=AlgFind;
	inline static AlgFind*& get_alg_ptr(ComputeThreadPriv* thread_priv) {
		return thread_priv->algFind;
	}
	inline static QString get_prefix() {
		return "compute.find";
	}
	inline static QString get_name() {
		return "Find";
	}
	inline static QString get_title() {
		return "Find Algorithm";
	}
	inline static const QList<const AlgInfo<AlgFind>*>* get_infos() {
		return &PluginManager::instance()->algsFind();
	}
};
template<> struct alg_helper<ALG_CONN> {
	using alg=AlgConn;
	inline static AlgConn*& get_alg_ptr(ComputeThreadPriv* thread_priv) {
		return thread_priv->algConn;
	}
	inline static QString get_prefix() {
		return "compute.connect";
	}
	inline static QString get_name() {
		return "Connect";
	}
	inline static QString get_title() {
		return "Connect Algorithm";
	}
	inline static const QList<const AlgInfo<AlgConn>*>* get_infos() {
		return &PluginManager::instance()->algsConn();
	}
};
template<> struct alg_helper<ALG_REFINE> {
	using alg=AlgRefine;
	inline static AlgRefine*& get_alg_ptr(ComputeThreadPriv* thread_priv) {
		return thread_priv->algRefine;
	}
	inline static QString get_prefix() {
		return "compute.refine";
	}
	inline static QString get_name() {
		return "Refine";
	}
	inline static QString get_title() {
		return "Refine Algorithm";
	}
	inline static const QList<const AlgInfo<AlgRefine>*>* get_infos() {
		return &PluginManager::instance()->algsRefine();
	}
};
template<> struct alg_helper<ALG_REFINE_AUTO> {
	using alg=AlgRefine;
	inline static AlgRefine*& get_alg_ptr(ComputeThreadPriv* thread_priv) {
		return thread_priv->algRefineAuto;
	}
	inline static QString get_prefix() {
		return "compute.autorefine";
	}
	inline static QString get_name() {
		return "Refine (auto)";
	}
	inline static QString get_title() {
		return "Refine Algorithm (Run Automatically)";
	}
	inline static const QList<const AlgInfo<AlgRefine>*>* get_infos() {
		return &PluginManager::instance()->algsRefine();
	}
};

// template<AlgType typ>
// ComputeAlgOptions<typ>* ComputeAlgOptions<typ>::instance() {
//     if(!_instance)
//         _instance=new ComputeAlgOptions<typ>{};
//     return _instance;
// }

template<AlgType typ>
void ComputeAlgOptions<typ>::config(ComputeThreadPriv* _ctp, Options* options) const {
	int idx=-1;
	std::vector<ParamValue> vals;
	auto defname=options->getString(alg_helper<typ>::get_prefix()+".default");
	if(!defname.isNull()) {
		for(int i=0; i<alg_helper<typ>::get_infos()->size(); i++) {
			if(defname==alg_helper<typ>::get_infos()->at(i)->name) {
				idx=i;
				break;
			}
		}
		if(idx!=-1) {
			auto& specs=alg_helper<typ>::get_infos()->at(idx)->paramSpecs;
			bool missing=false;
			for(size_t i=0; !missing && i<specs.size(); i++) {
				auto key=(alg_helper<typ>::get_prefix()+".params.%1.%2").arg(alg_helper<typ>::get_infos()->at(idx)->name).arg(i);
				auto str = key.toStdString();
				switch(specs[i].type()) {
					case ParamType::INT:
						{
							int v;
							if(options->getInt(key, &v))
								vals.push_back(ParamValue{v});
							else
								missing=true;
						}
						break;
					case ParamType::FLOAT:
						{
							double v;
							if(options->getDouble(key, &v))
								vals.push_back(ParamValue{v});
							else
								missing=true;
							std::cout << key <<"\t"<<v<< std::endl;

						}
						break;
					case ParamType::ENUM:
						{
							int v;
							if(options->getInt(key, &v))
								vals.push_back(ParamValue{v});
							else
								missing=true;
						}
						break;
				}
			}
			if(missing)
				vals.clear();
		}
	}
	if(idx==-1) {
		idx=idxDef;
		vals=valsDef;
	}
	auto& alg=alg_helper<typ>::get_alg_ptr(_ctp);
	alg=alg_helper<typ>::get_infos()->at(idx)->creater();
	alg->setCallback(sync_callback, _ctp);
	alg->setInfo(alg_helper<typ>::get_infos()->at(idx));
	for(int ch=0; ch<3; ch++)
		alg->updateCube(ch, _ctp->cuberefs[ch], _ctp->xfuncs[ch]);
	if(!vals.empty())
		alg->setParams(vals);
}

template<AlgType typ>
void ComputeAlgOptions<typ>::saveConfig(ComputeThreadPriv* _ctp, Options* options) const {
	auto& alg=alg_helper<typ>::get_alg_ptr(_ctp);
	options->setString(alg_helper<typ>::get_prefix()+".default", alg->info()->name);
	auto& specs=alg->info()->paramSpecs;
	auto vals=alg->getParams();
	for(size_t i=0; i<specs.size(); i++) {
		auto key=(alg_helper<typ>::get_prefix()+".params.%1.%2").arg(alg->info()->name).arg(i);
		switch(specs[i].type()) {
			case ParamType::INT:
				options->setInt(key, vals[i].getInt());
				break;
			case ParamType::FLOAT:
				options->setDouble(key, vals[i].getFloat());
				break;
			case ParamType::ENUM:
				options->setInt(key, vals[i].getEnum());
				break;
		}
	}
}

template<AlgType typ>
void ComputeAlgOptions<typ>::attach(ComputeThreadPriv* _ctp) {
	ctp=_ctp;
	if(!ctp) {
		if(idxDef!=idxSet) {
			chooser->setCurrentIndex(idxDef);
		} else {
			chgsDef.clear();
			for(size_t i=0; i<valsDef.size(); i++) {
				chgsDef.push_back(valsDef[i]!=valsSet[i]);
			}
			setParams(chgsDef, valsDef);
		}
		return;
	}
	auto& alg=alg_helper<typ>::get_alg_ptr(ctp);
	auto info=alg->info();
	int idx=-1;
	for(int i=0; i<alg_helper<typ>::get_infos()->size(); i++) {
		if(info==alg_helper<typ>::get_infos()->at(i)) {
			idx=i;
			break;
		}
	}
	if(idx==-1) {
		idx=0;
	}
	valsUsed=alg->getParams();
	idxUsed=idx;
	if(idxUsed!=idxSet) {
		chooser->setCurrentIndex(idxUsed);
	} else {
		chgsUsed.clear();
		for(size_t i=0; i<valsUsed.size(); i++) {
			chgsUsed.push_back(valsUsed[i]!=valsSet[i]);
		}
		setParams(chgsUsed, valsUsed);
	}
}

template<AlgType typ>
	ComputeAlgOptions<typ>::ComputeAlgOptions():
		OptionsPage{alg_helper<typ>::get_name(), alg_helper<typ>::get_title()},
		ctp{nullptr},
		disabled{true}
	{
		auto layout=this->layout();
		chooser=new QComboBox{this};
		layout->addWidget(chooser, 0);
		descLabel=new QLabel{this};
		layout->addWidget(descLabel, 0);
		descLabel->setWordWrap(true);
		layoutForm=new QFormLayout{};
		layout->addLayout(layoutForm, 1);
		 
		chooser->setEditable(false);
		idxDef=0;
		auto options=Tracer::instance()->options();
		auto defname=options->getString(alg_helper<typ>::get_prefix()+".default");
		for(int i=0; i<alg_helper<typ>::get_infos()->size(); i++) {
			chooser->addItem(alg_helper<typ>::get_infos()->at(i)->name);
			//std::cout << alg_helper<typ>::get_infos()->at(i)->name<<std::endl;  
			if(defname==alg_helper<typ>::get_infos()->at(i)->name)
				idxDef=i;
		}
		idxSet=idxDef;
		idxUsed=-1;
		chooser->setCurrentIndex(idxSet);
		connect(chooser, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &ComputeAlgOptions::algorithmChanged);
		algorithmChanged(idxSet);
	}
template<AlgType typ>
	void ComputeAlgOptions<typ>::algorithmChanged(int idx) {
		for(auto ent: entries) {
			delete ent;
		}
		for(auto ent: labels) {
			delete ent;
		}
		entries.clear();
		labels.clear();

		idxSet=idx;
		descLabel->setText(alg_helper<typ>::get_infos()->at(idxSet)->desc);
		auto& specs=alg_helper<typ>::get_infos()->at(idxSet)->paramSpecs;
		if(idxSet==idxUsed) {
			valsUsed=alg_helper<typ>::get_alg_ptr(ctp)->getParams();
			valsSet=valsUsed;
			chgsUsed.fill(false, valsSet.size());
		} else {
			valsUsed={};
			chgsUsed.resize(0);
			valsSet.clear();
		}
		valsDef.clear();
		auto global_opts=Tracer::instance()->options();
		chgsDef.clear();

		Options* session_opts=nullptr;
		if(ctp)
			session_opts=ctp->session->options();
		for(size_t i=0; i<specs.size(); i++) {
			auto& spec=specs[i];

			QWidget* ent;
			auto key=(alg_helper<typ>::get_prefix()+".params.%1.%2").arg(alg_helper<typ>::get_infos()->at(idxSet)->name).arg(i);
			switch(spec.type()) {
				case ParamType::INT:
					{
						valsDef.push_back(spec.asInt().initial);
						int v;
						if(global_opts->getInt(key, &v))
							valsDef[i]=v;
						if(idxSet!=idxUsed) {
							valsSet.push_back(valsDef[i]);
							if(session_opts && session_opts->getInt(key, &v))
								valsSet[i]=v;
						}
						auto box=new QSpinBox{this};
						box->setMinimum(spec.asInt().minimum);
						box->setMaximum(spec.asInt().maximum);
						if(spec.asInt().unit)
							box->setSuffix(spec.asInt().unit);
						box->setValue(valsSet[i].getInt());

						connect(box, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this, i](int v)->void {
								valsSet[i]=v;
								chgsDef[i]=v!=valsDef[i].getInt();
								if(idxSet==idxUsed) chgsUsed[i]=v!=valsUsed[i].getInt();
								notifyChange();});
						ent=box;
					}
					break;
				case ParamType::FLOAT:
					{
						valsDef.push_back(spec.asFloat().initial);
						double v;
						if(global_opts->getDouble(key, &v))
							valsDef[i]=v;
						if(idxSet!=idxUsed) {
							valsSet.push_back(valsDef[i]);
							if(session_opts && session_opts->getDouble(key, &v))
								valsSet[i]=v;
						}
						auto box=new QDoubleSpinBox{this};
						box->setMinimum(spec.asFloat().minimum);
						box->setMaximum(spec.asFloat().maximum);
						if(spec.asFloat().unit)
							box->setSuffix(spec.asFloat().unit);
						box->setValue(valsSet[i].getFloat());

						connect(box, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), [this, i](double v)->void { valsSet[i]=v; chgsDef[i]=v!=valsDef[i].getFloat(); if(idxSet==idxUsed) chgsUsed[i]=v!=valsUsed[i].getFloat(); notifyChange();});
						ent=box;
					}
					break;
				case ParamType::ENUM:
					{
						valsDef.push_back(spec.asEnum().initial);
						int v;
						if(global_opts->getInt(key, &v))
							valsDef[i].setEnum(v);
						if(idxSet!=idxUsed) {
							valsSet.push_back(valsDef[i]);
							if(session_opts && session_opts->getInt(key, &v))
								valsSet[i].setEnum(v);
						}
						auto box=new QComboBox(this);
						for(auto v: spec.asEnum().choices)
							box->addItem(v);
						box->setCurrentIndex(valsSet[i].getEnum());
						connect(box, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [this, i](int v)->void { valsSet[i]=v; chgsDef[i]=v!=valsDef[i].getEnum(); if(idxSet==idxUsed) chgsUsed[i]=v!=valsUsed[i].getEnum(); notifyChange();});
						ent=box;
					}
					break;
			}
			//std::cout << key.toStdString() << std::endl;
			if (key.toStdString() == "compute.connect.params.AStar.1"|| 
				key.toStdString() == "compute.connect.params.AStar.2" || 
				key.toStdString() == "compute.find.params.AStar.2"||
				key.toStdString() == "compute.find.params.AStar.3"
				)
				ent->setEnabled(false);
			chgsDef.push_back(valsDef[i]!=valsSet[i]);
			ent->setWhatsThis(spec.desc());
			auto lab=new QLabel{spec.name(), this};
			lab->setBuddy(ent);
			layoutForm->addRow(lab, ent);
			entries.push_back(ent);
			labels.push_back(lab);
		}
		notifyChange();
	}

template<AlgType typ>
	void ComputeAlgOptions<typ>::useDefaultImp() {
		if(idxSet!=idxDef) {
			chooser->setCurrentIndex(idxDef);
		} else {
			setParams(chgsDef, valsDef);
		}
	}
template<AlgType typ>
	void ComputeAlgOptions<typ>::saveDefaultImp() {
		idxDef=idxSet;
		valsDef=valsSet;
		chgsDef.fill(false, valsDef.size());
		saveOptions<true>(Tracer::instance()->options());
	}
template<AlgType typ>
	template<bool global>
		void ComputeAlgOptions<typ>::saveOptions(Options* options) {
			auto name=alg_helper<typ>::get_infos()->at(idxSet)->name;
			options->setString(alg_helper<typ>::get_prefix()+".default", name);
			auto& specs=alg_helper<typ>::get_infos()->at(idxSet)->paramSpecs;
			bool def=true;
			if(global) {
				for(size_t i=0; i<specs.size(); i++) {
					switch(specs[i].type()) {
						case ParamType::INT:
							if(specs[i].asInt().initial!=valsSet[i].getInt())
								def=false;
							break;
						case ParamType::FLOAT:
							if(specs[i].asFloat().initial!=valsSet[i].getFloat())
								def=false;
							break;
						case ParamType::ENUM:
							if(specs[i].asEnum().initial!=valsSet[i].getEnum())
								def=false;
							break;
					}
					if(!def)
						break;
				}
			} else {
				for(size_t i=0; i<specs.size(); i++) {
					if(valsDef[i]!=valsSet[i]) {
						def=false;
						break;
					}
				}
			}
			if(def) {
				for(size_t i=0; i<specs.size(); i++) {
					auto key=(alg_helper<typ>::get_prefix()+".params.%1.%2").arg(name).arg(i);
					options->removeKey(key);
				}
			} else {
				for(size_t i=0; i<specs.size(); i++) {
					auto key=(alg_helper<typ>::get_prefix()+".params.%1.%2").arg(name).arg(i);
					switch(specs[i].type()) {
						case ParamType::INT:
							options->setInt(key, valsSet[i].getInt());
							break;
						case ParamType::FLOAT:
							options->setDouble(key, valsSet[i].getFloat());
							break;
						case ParamType::ENUM:
							options->setInt(key, valsSet[i].getEnum());
							break;
					}
				}
			}
		}
template<AlgType typ>
	void ComputeAlgOptions<typ>::applyChangesImp() {
		if(!ctp)
			return;
		if(idxUsed!=idxSet) {
			idxUsed=idxSet;
			auto& alg=alg_helper<typ>::get_alg_ptr(ctp);
			delete alg;
			alg=alg_helper<typ>::get_infos()->at(idxUsed)->creater();
			alg->setCallback(sync_callback, ctp);
			alg->setInfo(alg_helper<typ>::get_infos()->at(idxUsed));
			for(int ch=0; ch<3; ch++)
				alg->updateCube(ch, ctp->cuberefs[ch], ctp->xfuncs[ch]);
		}
		valsUsed=valsSet;
		alg_helper<typ>::get_alg_ptr(ctp)->setParams(valsUsed);
		chgsUsed.fill(false, valsUsed.size());

		saveOptions<false>(ctp->session->options());
	}
template<AlgType typ>
	void ComputeAlgOptions<typ>::setParams(const QVector<bool>& chgs, const std::vector<ParamValue>& vals) {
		auto& specs=alg_helper<typ>::get_infos()->at(idxSet)->paramSpecs;
		for(size_t i=0; i<specs.size(); i++) {
			if(!chgs[i])
				continue;
			switch(specs[i].type()) {
				case ParamType::INT:
					static_cast<QSpinBox*>(entries[i])->setValue(vals[i].getInt());
					break;
				case ParamType::FLOAT:
					static_cast<QDoubleSpinBox*>(entries[i])->setValue(vals[i].getFloat());
					break;
				case ParamType::ENUM:
					static_cast<QComboBox*>(entries[i])->setCurrentIndex(vals[i].getEnum());
					break;
			}
		}
	}
template<AlgType typ>
	void ComputeAlgOptions<typ>::resetChangesImp() {
		if(!ctp)
			return;
		if(idxSet!=idxUsed) {
			chooser->setCurrentIndex(idxUsed);
		} else {
			setParams(chgsUsed, valsUsed);
		}
	}
template<AlgType typ>
	void ComputeAlgOptions<typ>::getState(bool* a, bool* b, bool* c, bool* d) const {
		if(ctp) {
			bool notApplied=idxSet!=idxUsed;
			for(int i=0; !notApplied && i<chgsUsed.size(); i++) {
				notApplied=notApplied||chgsUsed[i];
			}
			*a=notApplied&&!disabled;
			*b=notApplied;
		} else {
			*a=false;
			*b=false;
		}
		bool notDefault=idxSet!=idxDef;
		for(int i=0; !notDefault && i<chgsDef.size(); i++) {
			notDefault=notDefault||chgsDef[i];
		}
		*c=notDefault;
		*d=notDefault;
	}
template<AlgType typ>
	void ComputeAlgOptions<typ>::setStateImp(SessionState ts) {
		switch(ts) {
			case SessionState::Invalid:
			case SessionState::LoadingCatalog:
			case SessionState::Readonly:
			case SessionState::Computing:
				disabled=true;
				break;
			case SessionState::Ready:
			case SessionState::LoadingCubes:
				disabled=false;
				break;
		}
	}
	template<AlgType typ>
	ComputeAlgOptions<typ>::~ComputeAlgOptions() {
	}

#endif
